﻿namespace Lilium.Web.Mvc

module ActionLink =
    
    open System
    open System.Reflection
    open System.Web.Routing

    open Microsoft.FSharp.Quotations.Patterns
    
    open Monad.Maybe

    open Lilium.Prelude
    open Lilium.String

    let private getControllerArea (controllerType : Type) =
        
        let matchNamespace (pattern : string) (ns : string) =

            let matchComponents patternComp namespaceComp =
                match patternComp, namespaceComp with
                | "*", _ ->  true
                | _, _ when patternComp = namespaceComp ->  true
                | _ ->  false
          
            (pattern, ns) ||> on (Array.forall2 matchComponents) (fun a -> a.Split('.'))
        
        let areas = seq {
            for routeBase in RouteTable.Routes do
            match routeBase with
            | :? Route as route when route.DataTokens <> null
                ->  match route.DataTokens.["Namespaces"] with
                    | :? array<string> as namespaces when namespaces <> null
                        ->  for pattern in namespaces do
                            if  (not <| String.IsNullOrEmpty pattern) && 
                                (matchNamespace pattern controllerType.Namespace) 
                                then yield route.DataTokens.["area"] :?> string
                    | _  -> ()
            | _ ->  () }

        if (Seq.isEmpty areas)
            then ""
            else Seq.head areas

    let private getControllerName (controllerType : Type) =
        match controllerType.Name with
        | EndsWith "Controller" (prefix) -> prefix // MVC convention
        | EndsWith "C" (prefix) -> prefix // Lilium backing class
        | name -> name

    let private getActionName (meth : MethodInfo) =
        capitalize meth.Name

    let renderActionLink html linkText actionLink =

        let (|ActionMethod|_|) expr = maybe {
            let! (_, lambda) = (|Lambda|_|) expr
            let! (_, meth, args) = (|Call|_|) lambda
            return (meth, args) }

        match actionLink with 
        | ActionMethod (meth, args) 
            ->  let controllerType = meth.DeclaringType
                let controllerTypeName = controllerType.Name

                let routeValues = RouteValueDictionary()

                routeValues.["area"] <- getControllerArea controllerType
                routeValues.["controller"] <- getControllerName controllerType
                routeValues.["action"] <- getActionName meth

                //TODO: добавить аргументы в метод!

                System.Web.Mvc.Html.LinkExtensions.RouteLink(html, linkText, routeValues, null)
                
        | _ ->  failwith "Unable to match controller method for the `%O' expression" actionLink